"""
语音识别回调工具类

此模块提供语音识别相关的回调处理工具，包括：
- HTTP回调发送
- Webhook签名验证
- 回调重试机制
- 回调数据格式化

Authors: AI Assistant
Date: 2024-12-19
"""

import hashlib
import hmac
import json
import asyncio
import aiohttp
from typing import Dict, Any, Optional, List
from datetime import datetime, timedelta
from utils.log_util import logger


class SpeechCallbackUtil:
    """语音识别回调工具类"""
    
    # 默认配置
    DEFAULT_TIMEOUT = 30  # 默认超时时间(秒)
    DEFAULT_RETRY_TIMES = 3  # 默认重试次数
    DEFAULT_RETRY_DELAY = 5  # 默认重试延迟(秒)
    
    @staticmethod
    async def send_http_callback(
        url: str,
        data: Dict[str, Any],
        headers: Optional[Dict[str, str]] = None,
        timeout: int = DEFAULT_TIMEOUT,
        secret: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        发送HTTP回调请求
        
        Args:
            url: 回调URL
            data: 回调数据
            headers: 请求头
            timeout: 超时时间
            secret: 用于签名的密钥
            
        Returns:
            Dict[str, Any]: 回调结果
        """
        result = {
            'success': False,
            'status_code': None,
            'response_text': '',
            'error': None,
            'timestamp': datetime.now(),
            'duration': 0
        }
        
        try:
            start_time = datetime.now()
            
            # 准备请求头
            if headers is None:
                headers = {}
            
            headers.update({
                'Content-Type': 'application/json',
                'User-Agent': 'VoiceNote-Callback/1.0',
                'X-Callback-Timestamp': str(int(start_time.timestamp()))
            })
            
            # 准备请求数据
            json_data = json.dumps(data, ensure_ascii=False)
            
            # 添加签名
            if secret:
                signature = SpeechCallbackUtil.generate_signature(json_data, secret)
                headers['X-Callback-Signature'] = signature
            
            # 发送请求
            timeout_config = aiohttp.ClientTimeout(total=timeout)
            async with aiohttp.ClientSession(timeout=timeout_config) as session:
                async with session.post(
                    url,
                    data=json_data,
                    headers=headers
                ) as response:
                    result['status_code'] = response.status
                    result['response_text'] = await response.text()
                    result['success'] = 200 <= response.status < 300
                    
            # 计算耗时
            end_time = datetime.now()
            result['duration'] = (end_time - start_time).total_seconds()
            
            if result['success']:
                logger.info(f"回调发送成功: url={url}, status={result['status_code']}, duration={result['duration']}s")
            else:
                logger.warning(f"回调发送失败: url={url}, status={result['status_code']}, response={result['response_text']}")
                
        except asyncio.TimeoutError:
            result['error'] = f"回调请求超时 (>{timeout}s)"
            logger.error(f"回调超时: url={url}, timeout={timeout}s")
            
        except aiohttp.ClientError as e:
            result['error'] = f"网络请求错误: {str(e)}"
            logger.error(f"回调网络错误: url={url}, error={str(e)}")
            
        except Exception as e:
            result['error'] = f"回调发送异常: {str(e)}"
            logger.error(f"回调发送异常: url={url}, error={str(e)}")
            
        return result
    
    @staticmethod
    async def send_callback_with_retry(
        url: str,
        data: Dict[str, Any],
        max_retries: int = DEFAULT_RETRY_TIMES,
        retry_delay: int = DEFAULT_RETRY_DELAY,
        timeout: int = DEFAULT_TIMEOUT,
        secret: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        带重试机制的回调发送
        
        Args:
            url: 回调URL
            data: 回调数据
            max_retries: 最大重试次数
            retry_delay: 重试延迟(秒)
            timeout: 单次请求超时时间
            secret: 签名密钥
            
        Returns:
            Dict[str, Any]: 最终回调结果
        """
        last_result = None
        
        for attempt in range(max_retries + 1):
            try:
                result = await SpeechCallbackUtil.send_http_callback(
                    url=url,
                    data=data,
                    timeout=timeout,
                    secret=secret
                )
                
                if result['success']:
                    if attempt > 0:
                        logger.info(f"回调重试成功: url={url}, attempt={attempt + 1}")
                    return result
                    
                last_result = result
                
                # 如果还有重试机会，等待后继续
                if attempt < max_retries:
                    delay = retry_delay * (2 ** attempt)  # 指数退避
                    logger.info(f"回调失败，{delay}秒后进行第{attempt + 2}次尝试: url={url}")
                    await asyncio.sleep(delay)
                    
            except Exception as e:
                logger.error(f"回调重试异常: url={url}, attempt={attempt + 1}, error={str(e)}")
                last_result = {
                    'success': False,
                    'error': str(e),
                    'timestamp': datetime.now(),
                    'duration': 0,
                    'status_code': None,
                    'response_text': ''
                }
                
                if attempt < max_retries:
                    await asyncio.sleep(retry_delay)
        
        logger.error(f"回调最终失败: url={url}, 已重试{max_retries}次")
        return last_result or {'success': False, 'error': '未知错误'}
    
    @staticmethod
    def generate_signature(data: str, secret: str, algorithm: str = 'sha256') -> str:
        """
        生成回调签名
        
        Args:
            data: 要签名的数据
            secret: 签名密钥
            algorithm: 签名算法
            
        Returns:
            str: 签名字符串
        """
        try:
            if algorithm == 'sha256':
                signature = hmac.new(
                    secret.encode('utf-8'),
                    data.encode('utf-8'),
                    hashlib.sha256
                ).hexdigest()
                return f"sha256={signature}"
            elif algorithm == 'sha1':
                signature = hmac.new(
                    secret.encode('utf-8'),
                    data.encode('utf-8'),
                    hashlib.sha1
                ).hexdigest()
                return f"sha1={signature}"
            else:
                raise ValueError(f"不支持的签名算法: {algorithm}")
                
        except Exception as e:
            logger.error(f"生成签名失败: algorithm={algorithm}, error={str(e)}")
            raise
    
    @staticmethod
    def verify_signature(data: str, signature: str, secret: str) -> bool:
        """
        验证回调签名
        
        Args:
            data: 原始数据
            signature: 接收到的签名
            secret: 签名密钥
            
        Returns:
            bool: 验证是否通过
        """
        try:
            if not signature or not secret:
                return False
            
            # 解析签名算法
            if signature.startswith('sha256='):
                algorithm = 'sha256'
                received_signature = signature[7:]
            elif signature.startswith('sha1='):
                algorithm = 'sha1'
                received_signature = signature[5:]
            else:
                logger.warning(f"未知的签名格式: {signature}")
                return False
            
            # 生成期望的签名
            expected_signature = SpeechCallbackUtil.generate_signature(data, secret, algorithm)
            expected_signature = expected_signature.split('=', 1)[1]
            
            # 使用安全的比较方法
            return hmac.compare_digest(received_signature, expected_signature)
            
        except Exception as e:
            logger.error(f"验证签名失败: error={str(e)}")
            return False
    
    @staticmethod
    def format_speech_callback_data(
        speech_record: Dict[str, Any],
        callback_type: str = 'completed'
    ) -> Dict[str, Any]:
        """
        格式化语音识别回调数据
        
        Args:
            speech_record: 语音识别记录
            callback_type: 回调类型 (started|processing|completed|failed)
            
        Returns:
            Dict[str, Any]: 格式化的回调数据
        """
        callback_data = {
            'event': callback_type,
            'timestamp': datetime.now().isoformat(),
            'api_request_id': speech_record.get('api_request_id'),
            'speech_id': speech_record.get('speech_id'),
            'status': speech_record.get('status'),
            'data': {}
        }
        
        # 根据回调类型添加相应数据
        if callback_type == 'completed':
            callback_data['data'] = {
                'result_text': speech_record.get('result_text'),
                'confidence': speech_record.get('confidence'),
                'duration': speech_record.get('duration'),
                'file_size': speech_record.get('file_size'),
                'language': speech_record.get('language'),
                'model_name': speech_record.get('model_name'),
                'processing_time': speech_record.get('processing_time')
            }
        elif callback_type == 'failed':
            callback_data['data'] = {
                'error_message': speech_record.get('error_message'),
                'error_code': speech_record.get('error_code')
            }
        elif callback_type == 'processing':
            callback_data['data'] = {
                'progress': speech_record.get('progress', 0),
                'estimated_time': speech_record.get('estimated_time')
            }
        
        return callback_data
    
    @staticmethod
    async def batch_send_callbacks(
        callbacks: List[Dict[str, Any]],
        max_concurrent: int = 10
    ) -> List[Dict[str, Any]]:
        """
        批量发送回调
        
        Args:
            callbacks: 回调列表，每个包含url, data等信息
            max_concurrent: 最大并发数
            
        Returns:
            List[Dict[str, Any]]: 回调结果列表
        """
        semaphore = asyncio.Semaphore(max_concurrent)
        
        async def send_single_callback(callback_info: Dict[str, Any]) -> Dict[str, Any]:
            async with semaphore:
                url = callback_info['url']
                data = callback_info['data']
                secret = callback_info.get('secret')
                timeout = callback_info.get('timeout', SpeechCallbackUtil.DEFAULT_TIMEOUT)
                
                result = await SpeechCallbackUtil.send_callback_with_retry(
                    url=url,
                    data=data,
                    secret=secret,
                    timeout=timeout
                )
                
                return {
                    **result,
                    'callback_id': callback_info.get('callback_id'),
                    'url': url
                }
        
        # 并发发送所有回调
        tasks = [send_single_callback(callback) for callback in callbacks]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # 处理异常结果
        processed_results = []
        for i, result in enumerate(results):
            if isinstance(result, Exception):
                processed_results.append({
                    'success': False,
                    'error': str(result),
                    'callback_id': callbacks[i].get('callback_id'),
                    'url': callbacks[i]['url'],
                    'timestamp': datetime.now()
                })
            else:
                processed_results.append(result)
        
        return processed_results
    
    @staticmethod
    def validate_callback_url(url: str) -> bool:
        """
        验证回调URL的有效性
        
        Args:
            url: 回调URL
            
        Returns:
            bool: URL是否有效
        """
        try:
            if not url or not isinstance(url, str):
                return False
            
            # 基本URL格式检查
            if not (url.startswith('http://') or url.startswith('https://')):
                return False
            
            # 检查是否包含必要的域名部分
            if len(url.split('://')[1]) < 3:
                return False
            
            # 禁止内网地址（安全考虑）
            domain_part = url.split('://')[1].split('/')[0].split(':')[0]
            if domain_part in ['localhost', '127.0.0.1'] or domain_part.startswith('192.168.') or domain_part.startswith('10.'):
                return False
            
            return True
            
        except Exception as e:
            logger.error(f"验证回调URL失败: url={url}, error={str(e)}")
            return False
    
    @staticmethod
    def get_callback_retry_schedule(
        max_retries: int = DEFAULT_RETRY_TIMES,
        base_delay: int = DEFAULT_RETRY_DELAY
    ) -> List[datetime]:
        """
        生成回调重试时间计划
        
        Args:
            max_retries: 最大重试次数
            base_delay: 基础延迟时间(秒)
            
        Returns:
            List[datetime]: 重试时间列表
        """
        schedule = []
        now = datetime.now()
        
        for i in range(max_retries):
            # 指数退避：delay = base_delay * (2 ^ attempt)
            delay_seconds = base_delay * (2 ** i)
            retry_time = now + timedelta(seconds=delay_seconds)
            schedule.append(retry_time)
        
        return schedule 